// 编译器 遍历dom结构，解析插值表达式{{}}
class Compile {
  constructor(el, vm) {
    this.$vm = vm
    this.$el = document.querySelector(el)

    this.$fragment = this.node2fragement(this.$el) // 保存片段信息
    this.compile(this.$fragment) // 编译片段
    this.$el.appendChild(this.$fragment) // 将片段放回dom树
  }
  node2fragement(el) {
    const fragment = document.createDocumentFragment()
    let child
    // skill while循环 依次 将dom的第一个子节点取出，放入fragment，直至dom里没有节点
    while (child = el.firstChild) {
      fragment.appendChild(child) // skill 此操作会将el的子节点取出，做成片段
    }
    return fragment
  }
  // 是否是插值文本 {{dsadsa}}
  isIntent(node) {
    // skill 判断dom节点是否是文本节点，只需判断nodeType属性等于3即可
    return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }
  // 更新元素内容
  update(node, exp, dir) {
    const updator = this[dir + 'Updator']
    updator && updator(node, this.$vm[exp]) // 更新默认值

    // 触发依赖收集，并监听指定字段的变化
    new Watcher(this.$vm, exp, function (value) {
      updator && updator(node, value)
    })
  }
  textUpdator(node, value) {
    node.textContent = value
  }
  // 编译
  compile(fragment) {
    const childNodes = fragment.childNodes
    // 遍历片段结构，编译片段的每个子节点
    Array.from(childNodes).forEach(node => {
      // skill 判断dom节点是否是标签，只需判断nodeType属性等于1即可
      if (node.nodeType == 1) {
        this.compileElment(node)
        // console.log('编译标签' + node.nodeName);
      } else if (this.isIntent(node)) {
        this.compileText(node)
        // console.log('编译插值文本' + node.textContent);
      }
      // skill children和childNodes的区别是，前者只返回子代element的集合，而后者返回所有子元素集合，包括element、文本和注释
      if (node.childNodes.length > 0) this.compile(node)
    })
  }
  compileText(node) {
    const exp = RegExp.$1
    this.update(node, exp, 'text')
  }
  compileElment(node) {
    // k-xxx="yyy"
    Array.from(node.attributes).forEach(attr => {
      const attrName = attr.name // k-xxx
      const exp = attr.value // yyy
      if (attrName.indexOf('k-') === 0) {
        const dir = attrName.substring(2)
        this[dir] && this[dir](node, exp)
      }
    })
  }
  text(node, exp) {
    this.update(node, exp, 'text')
  }
}